#include "sheetwidget.h"
#include "ui_sheetwidget.h"

// CHARACTER MODEL
#include "toccharmodel.h"

// DEBUG
#include <QDebug>

// COMPLETER
#include <QCompleter>

// FILE I/O
#include <QFile>

// JSON
#include <qjson/serializer.h>
#include <qjson/qobjecthelper.h>
#include <qjson/parser.h>

// SIGNAL MAPPER
#include <QSignalMapper>

// HTML ITEM DELEGATE
#include "htmlitemdelegate.h"

// ComboMessageBox
#include <QScopedPointer>
#include "combomessagebox.h"

// MessageBox
#include <QMessageBox>

// Menu / ContextMenu
#include <QMenu>

SheetWidget::SheetWidget(QObject* chModel, QWidget *parent) :
    WidgetWithGdrData(parent),_is_loading(false), _ab_sort_enabled(true),
    ui(new Ui::SheetWidget)
{
    ui->setupUi(this);

    ui->cbAbilities->setItemDelegate(new HTMLItemDelegate());

    if ( _ab_sort_enabled )
    {
        sortcam.setDynamicSortFilter(true);
        sortcam.setSourceModel(&cam);
        ui->tvAbilities->setModel(&sortcam);
    }
    else
        ui->tvAbilities->setModel(&cam);

    if ( chModel == 0 )
        ch = new TocCharModel(this);
    else
        ch = chModel;

    // EDITS
    connect(ui->lePlName,       SIGNAL(textEdited(QString)),    ch, SLOT(setPlayerName(QString)));
    connect(ui->leInvName,      SIGNAL(textEdited(QString)),    ch, SLOT(setCharacterName(QString)));
    connect(ui->sbHit,          SIGNAL(valueChanged(int)),      ch, SLOT(setToHit(int)));
    connect(ui->sbBuildPoints,  SIGNAL(valueChanged(int)),      ch, SLOT(setBuildPoints(int)));
    connect(ui->sbSkillPoints,  SIGNAL(valueChanged(int)),      ch, SLOT(setSkillPoints(int)));
    connect(ui->lePillars,      SIGNAL(textEdited(QString)),    ch, SLOT(setPillars(QString)));
    connect(ui->leOccupBenef->document(),
            SIGNAL(contentsChanged()),
            this, SLOT(updateOccupBenefits()));

    // COMBO
    connect(ui->cbOccup, SIGNAL(activated(QString)), this,  SLOT(getOccupationBenefits(QString)));
    connect(ui->cbDrive, SIGNAL(activated(QString)), ch,    SLOT(setDrive(QString)));

    connect(ch, SIGNAL(playerNameChanged(QString)),         ui->lePlName,       SLOT(setText(QString)));
    connect(ch, SIGNAL(characterNameChanged(QString)),      ui->leInvName,      SLOT(setText(QString)));
    connect(ch, SIGNAL(occupationChanged(QString)),         this,               SLOT(comboSelectOccup(QString)));
    connect(ch, SIGNAL(occupationBenefitsChanged(QString)), ui->leOccupBenef,   SLOT(setPlainText(QString)));
    connect(ch, SIGNAL(driveChanged(QString)),              this,               SLOT(comboSelectDrive(QString)));
    connect(ch, SIGNAL(pillarsChanged(QString)),            ui->lePillars,      SLOT(setText(QString)));

    connect(ch, SIGNAL(toHitChanged(int)),          ui->sbHit,          SLOT(setValue(int)));
    connect(ch, SIGNAL(buildPointsChanged(int)),    ui->sbBuildPoints,  SLOT(setValue(int)));
    connect(ch, SIGNAL(skillPointsChanged(int)),    ui->sbSkillPoints,  SLOT(setValue(int)));

    // DATA BIND ( BUILD POINTS -> CHARACTER ABILITIES )
    connect(ch, SIGNAL(buildPointsChanged(int)),    &cam,               SLOT(setAvailableBuildPoints(int)));
    connect(ch, SIGNAL(skillPointsChanged(int)),    &cam,               SLOT(setAvailableInvestigationPoints(int)));

    // DATA BIND ( CHARACTER ABILITIES -> BUILD POINTS )
    connect(&cam, SIGNAL(buildPointsSpent(int)),         this,               SLOT(spendBuildPoints(int)));
    connect(&cam, SIGNAL(investigationPointsSpent(int)), this,               SLOT(spendInvestigationPoints(int)));    

    // DATA BIND ( OCCUPATION -> OCCUPATION BENEFITS )
    //connect(ch, SIGNAL(occupationChanged(QString)), SLOT(getOccupationBenefits(QString)));    
}

SheetWidget::~SheetWidget()
{
    delete ui;
}

QObject* SheetWidget::chModel()
{    
    return ch;
}

void SheetWidget::setGdrData(QVariantMap* data)
{
    gdrData = data;

    setupCompleters();
    setupFreePoints();
    setupAbilities();
}

void SheetWidget::refreshFromData(QObject* chModel)
{    
    ch = chModel;
    cam.fromVariantList(ch->property("Abilities").toList());
}

void SheetWidget::updateData(QObject* chModel)
{
    if ( chModel != 0 && ch != chModel )
        ch = chModel;
    ch->setProperty("Abilities", cam.toVariantList());
}

void SheetWidget::setupCompleters()
{
    // OCCUPATIONS
    QStringList ocpNames;
    foreach(QVariant qv, gdrData->value("occupations").toList())
        ocpNames << qv.toMap().value("name").toString();
    ui->cbOccup->addItems(ocpNames);
    //ui->leOccup->setCompleter(new QCompleter(ocpNames, this));

    // DRIVE
    QStringList drives = gdrData->value("drives").toStringList();
    //ui->leDrive->setCompleter(new QCompleter(drives, this));
    ui->cbDrive->addItems(drives);
}

void SheetWidget::setupFreePoints()
{
    if ( _is_loading )
    {
        qDebug() << "Loading: skip setupFreePoints()";
        return;
    }

    // FREE INITIAL POINTS
    QVariantMap free_points = gdrData->value("freepoints").toMap();

    foreach(QString key, free_points.keys())
        cam.addAbility(key, tr("General"), free_points.value(key).toInt(), free_points.value(key).toInt());

    checkHealthAbility();
}

void SheetWidget::setupAbilities()
{
    QVariantMap abCategs = gdrData->value("abilities").toMap();
    foreach(QString key, abCategs.keys())
    {
        QVariantMap subMap = abCategs.value(key).toMap();
        if ( subMap.empty() )
        {
            setupAbilities(key, QString(), abCategs.value(key).toList());
        }
        else
        {
            foreach(QString subkey, subMap.keys())
            {
                setupAbilities(key, subkey, subMap.value(subkey).toList());
            }
        }
    }

    ui->cbAbilities->setModel(&alm);
}

void SheetWidget::setupAbilities(QString categ, QString subcateg, QVariantList list)
{
    alm.addAbility(QString(), categ, subcateg, "<i><span style=\"color:gray\">%1</span></i>");
    foreach(QVariant qv, list)
        alm.addAbility(qv.toString(), categ, subcateg);        
}

void SheetWidget::getOccupationBenefits(QString name /*Occupation Name*/)
{
    bool found = false;
    QString oldOccupation = ch->property("Occupation").toString();
    QVariant data_ocp;

    if ( name == oldOccupation )
    {
        qDebug() << "Changing to same occupation. skipping";
        return;
    }

    QVariantList occupations = gdrData->value("occupations").toList();
    for(int i = 0; i < occupations.count(); ++i)
    {
        data_ocp = occupations.at(i);
        if ( data_ocp.toMap()["name"] == name )
        {
            found = true;
            break;
        }
    }

    if ( !found )
    {
        qDebug() << "Invalid occupation.";
        return;
    }

    if ( _is_loading )
    {
        qDebug() << "Loading: skip occupation change.";
        return;
    }

    if ( !oldOccupation.isEmpty() && !askToChangeOccupation() )
    {
        qDebug() << "Cancel occupation change. oldOccupation = " << oldOccupation;

        /*
        ui->leOccup->blockSignals(true);
        ui->leOccup->setText(oldOccupation);
        ui->leOccup->blockSignals(false);*/
        ui->cbOccup->setEditText(oldOccupation);
    }
    else
    {
        cam.removeAll();

        setupFreePoints();

        QVariantList occupAbilities = extractOccupAbilities(name);
        foreach(QVariant qv, occupAbilities)
        {
            QString name = qv.toString();
            if ( name.startsWith(QChar('*')) )
            {
                name = askToChooseAbility(name);
            }

            if ( !name.isEmpty() )
                cam.addAbility(name, alm.getCategory(name), 0, 0, true);
        }

        ch->setProperty("Occupation", name);
        ch->setProperty("OccupationBenefits", data_ocp.toMap().value("benefits"));
    }
}

void SheetWidget::onIntPropertyChanged(QString prop)
{
    QSignalMapper* mapper = qobject_cast<QSignalMapper*>(sender());
    if (mapper == 0)
    {
        qCritical("Signal Mapper is 0.");
        return;
    }

    int oldValue = ch->property(qPrintable(prop)).toInt();
    int newValue = mapper->mapping(prop)->property("value").toInt();
    int cost = newValue - oldValue;
    if ( canSpendBuildPoints(cost) )
    {
        spendBuildPoints(cost);
        ch->setProperty(qPrintable(prop), newValue);
    }
    else
        mapper->mapping(prop)->setProperty("value", oldValue);
}

bool SheetWidget::canSpendBuildPoints(int points)
{
    int currentPoints = ch->property("BuildPoints").toInt();
    return currentPoints >= points;
}

bool SheetWidget::canSpendInvestigationPoints(int points)
{
    int currentPoints = ch->property("SkillPoints").toInt();
    return currentPoints >= points;
}

void SheetWidget::spendBuildPoints(int points)
{
    int currentPoints = ch->property("BuildPoints").toInt();
    int newValue = currentPoints - points;
    ch->setProperty("BuildPoints", newValue);

    checkHealthAbility();
}

void SheetWidget::spendInvestigationPoints(int points)
{
    int currentPoints = ch->property("SkillPoints").toInt();
    int newValue = currentPoints - points;
    ch->setProperty("SkillPoints", newValue);
}

void SheetWidget::checkHealthAbility()
{
    int healthRank = cam.getAbilityRank(tr("Athletics"));
    ch->setProperty("ToHit", healthRank < 8 ? 3 : 4);

    qDebug() << "Athletics Rank: " << healthRank << " ToHit: " << ch->property("ToHit");
}

void SheetWidget::updateOccupBenefits()
{
    // HACK to prevent infinite SIG-SLOT loop.
    ch->blockSignals(true);
    ch->setProperty("OccupationBenefits", ui->leOccupBenef->toPlainText());
    ch->blockSignals(false);
}

QVariantList SheetWidget::extractOccupAbilities(QString name)
{
    QVariantList occupations = gdrData->value("occupations").toList();
    for(int i = 0; i < occupations.count(); ++i)
    {
        QVariantMap ocp = occupations.at(i).toMap();
        if ( ocp["name"] == name )
        {
            return ocp["abilities"].toList();
        }
    }

    return QVariantList();
}

QString SheetWidget::askToChooseAbility(QString filter)
{
    QScopedPointer<AbilityListModel> model(alm.clone());
    QStringList filters = cam.abilityNames();
    filters << filter;
    //model->applyFilter(filter);
    model->applyFilters(filters);

    QScopedPointer<ComboMessageBox> dlg(new ComboMessageBox(this));
    dlg->setComboModel(model.data());
    dlg->setLabelText(tr("Please choose an ability from the list and press OK."));

    if ( dlg->exec() == QDialog::Accepted )
    {
        return dlg->selectedText();
    }

    return "";
}

bool SheetWidget::askToChangeOccupation()
{
    int bt = QMessageBox::question(this,
                 qApp->applicationName(),
                 tr("Changing your character occupation will reset all the abilities.\nAre you sure?"),
                 QMessageBox::Yes, QMessageBox::No);

    qDebug() << "askToChangeOccupation: " << bt;
    return bt == QMessageBox::Yes;
}

void SheetWidget::comboSelectOccup(QString value)
{
    ui->cbOccup->setCurrentIndex(ui->cbOccup->findText(value));
}

void SheetWidget::comboSelectDrive(QString value)
{
    ui->cbDrive->setCurrentIndex(ui->cbDrive->findText(value));
}


void SheetWidget::on_pbAddSkill_clicked()
{
    QString abil = ui->cbAbilities->currentText();
    QString categ = alm.getCategory(ui->cbAbilities->currentIndex());
    qDebug() << "Selected Ability: " << abil << "\nCateg: " << alm.getCategory(ui->cbAbilities->currentIndex());
    cam.addAbility(abil, categ);
}

void SheetWidget::on_pbRemoveSkill_clicked()
{
    QModelIndexList list = ui->tvAbilities->selectionModel()->selectedIndexes();
    if ( list.count() <= 0 )
        return;

    QModelIndex idx = list.at(0);
    cam.removeAt(idx);
}

void SheetWidget::on_pbLock_toggled(bool checked)
{
    ui->pbAddSkill->setEnabled(!checked);
    ui->pbRemoveSkill->setEnabled(!checked);

    cam.setAvailableBuildPoints(checked ? 0 : ch->property("BuildPoints").toInt());
    cam.setAvailableInvestigationPoints(checked ? 0 : ch->property("SkillPoints").toInt());
}

void SheetWidget::on_pbResetPool_clicked()
{
    foreach(QString ab, cam.abilityNames())
    {
        qDebug() << "Reset pool of: " << ab;
        cam.setAbilityPool(ab, cam.getAbilityRank(ab));
    }
}

void SheetWidget::on_tvAbilities_customContextMenuRequested(QPoint pos)
{
    QPoint gpos = ui->tvAbilities->viewport()->mapToGlobal(pos);
    QModelIndex idx = QModelIndex();
    if ( _ab_sort_enabled )
        idx = sortcam.mapToSource(ui->tvAbilities->indexAt(pos));
    else
        idx = ui->tvAbilities->indexAt(pos);

    if ( !idx.isValid() )
        return;

    QString abName = cam.getAbilityName(idx);
    QScopedPointer<QMenu> pmenu(new QMenu(abName, ui->tvAbilities));
    QScopedPointer<QAction> actName(pmenu->addAction(abName));
    actName->setEnabled(false);

    pmenu->addSeparator();
    QScopedPointer<QAction> actRemove(pmenu->addAction(tr("Remove")));
    QScopedPointer<QAction> actResetPool(pmenu->addAction(tr("Reset Pool")));
    QScopedPointer<QAction> actMkProfession(pmenu->addAction(tr("Profession Skill")));
    actMkProfession->setCheckable(true);
    actMkProfession->setChecked(cam.isProfessionSkill(idx));

    QAction* choosen = pmenu->exec(gpos);

    if ( choosen == actMkProfession.data() )
        cam.setProfessionSkill(idx, choosen->isChecked());
    else if ( choosen == actRemove.data() )
        cam.removeAt(idx);
    else if ( choosen == actResetPool.data() )
        cam.setAbilityPool(idx, cam.getAbilityRank(idx));
}
